package com.element.oauth2.token;

import com.common.core.exception.ExceptionFactory;
import com.element.oauth2.constant.SecurityParams;
import io.netty.util.internal.StringUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;

import javax.annotation.Resource;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * OAuth2授权信息持久化
 * 记录授权的资源拥有者（Resource Owner）对某个客户端的某次授权记录
 */
@Configuration
public class RedisOAuth2AuthorizationService implements OAuth2AuthorizationService {

    /**
     * 授权确认时间(秒)
     */
    private final static Long TIMEOUT = 60L * 10;

    @Resource
    private RedisTemplate<String, OAuth2Authorization> redisTemplate;

    @Override
    public void save(OAuth2Authorization authorization) {
        if (null == authorization) {
            throw ExceptionFactory.sysException("授权信息为空");
        }
        // 状态码
        if (isState(authorization)) {
            String token = authorization.getAttribute(OAuth2ParameterNames.STATE);
            redisTemplate.setKeySerializer(RedisSerializer.string());
            redisTemplate.setValueSerializer(RedisSerializer.java());
            redisTemplate.opsForValue().set(buildAuthKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT, TimeUnit.SECONDS);
        }
        // 授权码
        if (isCode(authorization)) {
            OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization.getToken(OAuth2AuthorizationCode.class);
            if (null == authorizationCode) {
                return;
            }
            OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
            if (null == authorizationCodeToken || null == authorizationCodeToken.getIssuedAt()) {
                return;
            }
            long between = ChronoUnit.SECONDS.between(authorizationCodeToken.getIssuedAt(), authorizationCodeToken.getExpiresAt());
            redisTemplate.setKeySerializer(RedisSerializer.string());
            redisTemplate.setValueSerializer(RedisSerializer.java());
            redisTemplate.opsForValue().set(buildAuthKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), authorization, between, TimeUnit.SECONDS);
        }
        // 认证token
        if (isAccessToken(authorization)) {
            OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
            if (null == accessToken || null == accessToken.getIssuedAt()) {
                return;
            }
            long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
            redisTemplate.setKeySerializer(RedisSerializer.string());
            redisTemplate.setValueSerializer(RedisSerializer.java());
            redisTemplate.opsForValue().set(SecurityParams.SPRING_SECURITY_USER_TOKEN_KEY(authorization, authorization.getId()), authorization, between, TimeUnit.SECONDS);
            redisTemplate.opsForValue().set(buildAccessTokenKey(accessToken.getTokenValue()), authorization, between, TimeUnit.SECONDS);
        }
        // 刷新Token
        if (isRefreshToken(authorization)) {
            OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = authorization.getRefreshToken();
            if (null == refreshToken) {
                return;
            }
            OAuth2RefreshToken token = refreshToken.getToken();
            if (null == token || null == token.getIssuedAt()) {
                return;
            }
            long between = ChronoUnit.SECONDS.between(token.getIssuedAt(), token.getExpiresAt());
            redisTemplate.setKeySerializer(RedisSerializer.string());
            redisTemplate.setValueSerializer(RedisSerializer.java());
            redisTemplate.opsForValue().set(buildRefreshTokenKey(token.getTokenValue()), authorization, between, TimeUnit.SECONDS);
        }
    }

    @Override
    public void remove(OAuth2Authorization authorization) {
        if (null == authorization) {
            throw ExceptionFactory.sysException("授权信息为空");
        }
        // 状态码
        if (isState(authorization)) {
            String token = authorization.getAttribute(OAuth2ParameterNames.STATE);
            redisTemplate.delete(buildAuthKey(OAuth2ParameterNames.STATE, token));
        }
        // 授权码
        if (isCode(authorization)) {
            OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization.getToken(OAuth2AuthorizationCode.class);
            if (null == authorizationCode) {
                return;
            }
            OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
            redisTemplate.delete(buildAuthKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()));
        }
        // 认证token
        if (isAccessToken(authorization)) {
            OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
            redisTemplate.delete(buildAccessTokenKey(accessToken.getTokenValue()));
        }
        // 刷新Token
        if (isRefreshToken(authorization)) {
            OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = authorization.getRefreshToken();
            if (null == refreshToken) {
                return;
            }
            OAuth2RefreshToken token = refreshToken.getToken();
            if (null == token || null == token.getIssuedAt()) {
                return;
            }
            redisTemplate.delete(buildRefreshTokenKey(token.getTokenValue()));
        }
        // redisOperationService.delete(SecurityParams.SPRING_SECURITY_USER_TOKEN_KEY(authorization, authorization.getId()));
    }

    @Override
    @Nullable
    public OAuth2Authorization findById(String id) {
        return redisTemplate.opsForValue().get(id);
    }

    @Override
    @Nullable
    public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) {
        if (StringUtil.isNullOrEmpty(token) || null == tokenType) {
            throw ExceptionFactory.sysException("token获取tokenType为空");
        }
        String key;
        if (OAuth2TokenType.ACCESS_TOKEN.getValue().equals(tokenType.getValue())) {
            key = buildAccessTokenKey(token);
        } else if (OAuth2TokenType.REFRESH_TOKEN.getValue().equals(tokenType.getValue())) {
            key = buildRefreshTokenKey(token);
        } else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
            key = buildAuthKey(OAuth2ParameterNames.STATE, token);
        } else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
            key = buildAuthKey(OAuth2ParameterNames.CODE, token);
        } else {
            return null;
        }
        return redisTemplate.opsForValue().get(key);
    }

    private String buildAuthKey(String type, String id) {
        return SecurityParams.SPRING_SECURITY_AUTH_KEY + type + ":" + id;
    }

    private String buildAccessTokenKey(String id) {
        return SecurityParams.SPRING_SECURITY_TOKEN_KEY + "access:" + id;
    }

    private String buildRefreshTokenKey(String id) {
        return SecurityParams.SPRING_SECURITY_TOKEN_KEY + "refresh:" + id;
    }

    private boolean isState(OAuth2Authorization authorization) {
        return Objects.nonNull(authorization.getAttribute(OAuth2ParameterNames.STATE));
    }

    private boolean isCode(OAuth2Authorization authorization) {
        OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization.getToken(OAuth2AuthorizationCode.class);
        return Objects.nonNull(authorizationCode);
    }

    private boolean isRefreshToken(OAuth2Authorization authorization) {
        return Objects.nonNull(authorization.getRefreshToken());
    }

    private boolean isAccessToken(OAuth2Authorization authorization) {
        return Objects.nonNull(authorization.getAccessToken());
    }
}